<?php
/**
 * Created by PhpStorm.
 * User: HS032
 * Date: 2020/7/24
 * Time: 19:10
 */

namespace pay\libs;

use pay\modules\frontend\models\Configuration;
use yii\base\Exception;
use Yii;
use yii\log\FileTarget;

class WxPayApi
{

    private static $appid = '';
    private static $mch_id = '';
    private static $key = '';
    private static $appSecret = '';
    private static $cookie = null;
    private static $value = '';
    private static $certificate_cert  = '';
    private static $certificate_key= '';
//    private static $app_notify_url = 'http://paycentre.horsevision.cn/frontend/pay-callback/call-wx-back-url';
    private static $app_notify_url = 'http://pay.raysfly.com/frontend/pay-callback/call-wx-back-url';


    public function __construct($channelMerId)
    {
        $configModel = new Configuration();
        $configData = $configModel->Configuration($channelMerId);
        if (!empty($configData)){
            $this::$appid = $configData['appId'];
            $this::$appSecret = $configData['appSecret'];
            $this::$mch_id = $configData['mchId'];
            $this::$key = $configData['key'];
            if (!empty($configData['certLocalPath'])){
                $content = explode(',',$configData['certLocalPath']);
                $this::$certificate_cert = Yii::$app->getBasePath().$content['0'];
                $this::$certificate_key = Yii::$app->getBasePath().$content['1'];
            }

        }else{
            echo  Tools::showRes(99,'数据初始化失败，缺少配置信息');
            die;
        }

    }


    /**
     *
     * 统一下单，WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayConfigInterface $config  配置对象
     * @param WxPayUnifiedOrder $inputObj
     * @param int $timeOut
     * @throws Exception
     * @return 成功时返回，其他抛异常
     */
    public  function unifiedOrder( $data, $timeOut = 6)
    {
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

        try{
            //检测必填参数
            if(!$data['pay_order_sn']) {
                throw new Exception("缺少统一支付接口必填参数out_trade_no！");
            }else if(!$data['aid']){
                throw new Exception("缺少统一支付接口必填参数body！");
            }else if(!$data['amount']) {
                throw new Exception("缺少统一支付接口必填参数total_fee！");
            }
            if (!isset($data['tradeType']) || empty($data['tradeType'])){
                $data['tradeType'] = "JSAPI";
            }

            self::setPayValue($data);
            //关联参数
            if($data['tradeType'] == "JSAPI" && !$data['userId']){
                throw new Exception("统一支付接口中，缺少必填参数openid！trade_type为JSAPI时，openid为必填参数！");
            }else{
                self::$value['openid'] = $data['userId'];
            }
            if($data['tradeType'] == "NATIVE" && !$data['goodsId']){
                throw new Exception("统一支付接口中，缺少必填参数product_id！NATIVE，product_id为必填参数！");
            }else{
                self::$value['product_id'] = $data['goodsId'];
            }

            if($data['tradeType'] == 'MWEB' && !$data['sceneInfo']){
                throw new Exception("统一支付接口中，缺少必填参数 sceneInfo！MWEB，sceneInfo 为必填参数！");
            }else{
                self::$value['scene_info'] = $data['sceneInfo'];
            }

            //异步通知url未设置，则使用配置文件中的url

            //签名
            $sign = self::MakeSign();
            $xml = self::ToXml();
            $response = self::postXmlCurl($url, $xml);
            $result = self::Setpackage($response);
            if ($result['code'] !== 0){
                return $result;
            }

            if ($data['tradeType'] == "JSAPI"){
                self::$value = [
                    'appId' => self::$appid,
                    'timeStamp' => time(),
                    'nonceStr' => self::getNonceStr(),
                    'package' => 'prepay_id='.$result['msg']['prepay_id'],
                    'signType' => 'MD5',
                ];
                self::$value['paySign'] = self::MakeSign();
                return ['code' => 0,'msg' => self::$value];
            }elseif($data['tradeType'] == "NATIVE"){
                return ['code' => 0,'msg' => $result['msg']['code_url']];
            }
            return $result;

        }catch (Exception $e){
            return ['code' => 99,'msg' => $e->getMessage()];
        }


    }



    /**
     *
     * 查询订单，WxPayOrderQuery中out_trade_no、transaction_id至少填一个
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayConfigInterface $config  配置对象
     * @param WxPayOrderQuery $inputObj
     * @param int $timeOut
     * @throws Exception
     * @return 成功时返回，其他抛异常
     */
    public  function orderQuery($data, $timeOut = 6)
    {
        $url = "https://api.mch.weixin.qq.com/pay/orderquery";
        try{
            self::setQueryOrder($data);
            //检测必填参数
            if($data['pay_order_sn'] and !$data['wechatSn']) {
                self::$value['out_trade_no'] = $data['pay_order_sn'];
            }elseif(!$data['pay_order_sn'] and $data['wechatSn']){
                self::$value['transaction_id'] = $data['wechatSn'];
            }else{
                throw new Exception("订单查询接口中，out_trade_no、transaction_id至少填一个！");
            }

            //签名
            $sign = self::MakeSign();
            $xml = self::ToXml();

            $response = self::postXmlCurl($url, $xml);
            $result = self::Setpackage($response);
            return $result;

        }catch (Exception $e){
            return ['code' => 99,'msg' => $e->getMessage()];
        }

    }


    /**** 关闭订单接口
     * @param $data pay_order_sn：订单号； signtype：签名类型
     * @param int $timeOut
     * @return array
     * @throws Exception
     */
    public  function closeOrder($data, $timeOut = 6)
    {
        $url = "https://api.mch.weixin.qq.com/pay/closeorder";
        self::setQueryOrder($data);
        //检测必填参数
        if(!$data['pay_order_sn']) {
            throw new Exception("订单查询接口中，out_trade_no必填！");
        }else{
            self::$value['out_trade_no'] = $data['pay_order_sn'];
        }
        //签名
        $sign = self::MakeSign();
        $xml = self::ToXml();
        $response = self::postXmlCurl($url, $xml);
        $result = self::Setpackage($response);
        return $result;
    }


    /**** 退款接口
     * @param $data
     * @param int $timeOut
     * @return array
     */
    public  function refund($data, $timeOut = 6)
    {
        $url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        try{
            //检测必填参数
            if(!$data['paySerialId'] && !$data['wechatSn']) {
                throw new Exception("退款申请接口中，out_trade_no、transaction_id至少填一个！");
            }else if(!$data['refundUserSerial']){
                throw new Exception("退款申请接口中，缺少必填参数out_refund_no！");
            }else if(!$data['totalFee']){
                throw new Exception("退款申请接口中，缺少必填参数total_fee！");
            }else if(!$data['refundFee']){
                throw new Exception("退款申请接口中，缺少必填参数refund_fee！");
            }else if(!$data['payUserId']){
                throw new Exception("退款申请接口中，缺少必填参数payUserId！");
            }

            self::setRefundValue($data);
            if (!$data['paySerialId'] and $data['wechatSn']){
                self::$value['transaction_id'] = $data['wechatSn'];
            }else if ($data['paySerialId'] and !$data['wechatSn']){
                self::$value['out_trade_no'] = $data['paySerialId'];
            }
            if (empty(self::$certificate_cert) or empty(self::$certificate_key)){
                throw new Exception("缺少证书,请在后台上传证书！");
            }
            //签名
            $sign = self::MakeSign();
            $xml = self::ToXml();

            $response = self::postXmlCurl($url, $xml,'post',true);
            self::recordLogin(109999,'参数',$response);
            $result = self::Setpackage($response);
            return $result;

        }catch (Exception $e){
            return ['code' => 99,'msg' => $e->getMessage()];
        }

    }


    /**
     *
     * 查询退款
     * 提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，
     * 用零钱支付的退款20分钟内到账，银行卡支付的退款3个工作日后重新查询退款状态。
     * WxPayRefundQuery中out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayConfigInterface $config  配置对象
     * @param WxPayRefundQuery $inputObj
     * @param int $timeOut
     * @throws WxPayException
     * @return 成功时返回，其他抛异常
     */
    public function refundQuery($data, $timeOut = 6)
    {
        $url = "https://api.mch.weixin.qq.com/pay/refundquery";
        //检测必填参数
        if(!$data['pay_order_sn'] &&
            !$data['wechatSn'] &&
            !$data['refundSn'] &&
            !$data['refundId']) {
            throw new WxPayException("退款查询接口中，out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个！");
        }elseif($data['pay_order_sn']){
            self::$value['out_trade_no'] = $data['pay_order_sn'];
        }elseif($data['wechatSn']){
            self::$value['transaction_id'] = $data['wechatSn'];
        }elseif ($data['refundSn']){
            self::$value['out_refund_no'] = $data['refundSn'];
        }else{
            self::$value['refund_id'] = $data['refundId'];
        }
        self::setRefundQueryValue();

        //签名
        $sign = self::MakeSign();
        $xml = self::ToXml();

        $response = self::postXmlCurl($url, $xml);
        $result = self::Setpackage($response);
        return $result;


    }



    /**
     * 生成签名
     * @param WxPayConfigInterface $config  配置对象
     * @param bool $needSignType  是否需要补signtype
     * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    public static function MakeSign($signType = 'MD5')
    {

        //签名步骤一：按字典序排序参数
        ksort(self::$value);
        $string = self::ToUrlParams();
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=".self::$key;
        //签名步骤三：MD5加密或者HMAC-SHA256
        if($signType == "MD5"){
            $string = md5($string);
        } else if($signType == "HMAC-SHA256") {
            $string = hash_hmac("sha256",$string ,self::$key);
        } else {
            throw new Exception("签名类型不支持！");
        }

        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        self::$value['sign'] = $result;
        return $result;
    }
    /**
     * 格式化参数格式化成url参数
     */
    public static function ToUrlParams()
    {
        $buff = "";
        foreach (self::$value as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }

        $buff = trim($buff, "&");
        return $buff;
    }



    /**
     *
     * 产生随机字符串，不长于32位
     * @param int $length
     * @return 产生的随机字符串
     */
    public static function getNonceStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }


    /**
     * 输出xml字符
     * @throws Exception
     **/
    public static function ToXml()
    {
        if(!is_array(self::$value) || count(self::$value) <= 0)
        {
            throw new Exception("数组数据异常！");
        }

        $xml = "<xml>";
        foreach (self::$value as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }


    /*** 设置支付参数
     * @param $data
     */
    public static function setPayValue($data)
    {

        $data['nonceStr'] = self::getNonceStr();
        $params = [
            "appid" => self::$appid,//小程序ID
            "mch_id" => self::$mch_id,//商户号

//            "device_info" => $device_info,//设备号
            "nonce_str" => $data['nonceStr'],//随机字符串
            // "sign" => $sign,//签名-----------
            "body" => isset($data['goodsDesc'])?$data['goodsDesc']:$data['aid'],//商品描述
            // "detail" => "",//商品详情
            "attach" => isset($data['attach'])?$data['attach']:'备注',//附加数据 订单备注
            "out_trade_no" => $data['pay_order_sn'],//商户订单号
            "total_fee" => $data['amount'],//标价金额
//            "spbill_create_ip" => Yii::$app->request->userIP,//终端IP
            // "goods_tag" => "",//订单优惠标记
            "notify_url" => self::$app_notify_url,//通知地址
            "trade_type" => isset($data['tradeType'])?$data['tradeType']:'JSAPI',//交易类型
        ];

        self::$value = $params;
    }

    /*** 设置查询、关闭订单参数
     * @param $data
     */
    public static function setQueryOrder($data)
    {
        $data['nonceStr'] = self::getNonceStr();
        $params = [
            "appid" => self::$appid,//小程序ID
            "mch_id" => self::$mch_id,//商户号
            "nonce_str" => $data['nonceStr'],//随机字符串
        ];

        self::$value = $params;
    }


    /*** 生成退款参数
     * @param $data
     */
    public static function setRefundValue($data)
    {
        $data['nonceStr'] = self::getNonceStr();
        $params = [
            "appid" => self::$appid,//小程序ID
            "mch_id" => self::$mch_id,//商户号

//            "device_info" => $device_info,//设备号
            "nonce_str" => $data['nonceStr'],//随机字符串
            // "sign" => $sign,//签名-----------
            "out_refund_no" => $data['refundUserSerial'],// 退款单号
            "total_fee" => $data['totalFee'],//标价金额
            "refund_fee" => $data['refundFee'],//退款金额
            "notify_url" => isset($data['notify_url'])?$data['notify_url']:'',//通知地址
        ];

        self::$value = $params;
    }

    public static function setRefundQueryValue()
    {
        $data['nonceStr'] = self::getNonceStr();
        $params = [
            "appid" => self::$appid,//小程序ID
            "mch_id" => self::$mch_id,//商户号
//            "device_info" => $device_info,//设备号
            "nonce_str" => $data['nonceStr'],//随机字符串
            // "sign" => $sign,//签名-----------
        ];

        self::$value = $params;
    }



    public static function postXmlCurl($url, $param, $method = 'post' , $useCert = false ,$second = 30)
    {
        // 初始华
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_CERTINFO, true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        //curl_setopt($curl, CURLOPT_VERBOSE, true); //打印日志

        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);//函数中加入下面这条语句
        curl_setopt($curl, CURLOPT_TIMEOUT, $second);


        if (array_key_exists("HTTP_USER_AGENT", $_SERVER)) {
            curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
        }

        if (self::$cookie) {
            curl_setopt($curl, CURLOPT_COOKIE, self::$cookie);
        }

        if ($useCert == true) {
            $sslCertPath =  self::$certificate_cert;
            $sslKeyPath =  self::$certificate_key;
//            var_dump($sslKeyPath,$sslCertPath);die ;
            self::recordLogin('1000',$sslCertPath,$sslKeyPath);
            //设置证书
            //使用证书：cert 与 key 分别属于两个.pem文件
            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLCERT, $sslCertPath); //'/www/wwwroot/jlbwx.cnkly.com/jlbwx/web/cert/apiclient_cert.pem'
            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLKEY, $sslKeyPath);//'/www/wwwroot/jlbwx.cnkly.com/jlbwx/web/cert/apiclient_key.pem'
        }

        // post处理
        if ($method == 'post') {
            curl_setopt($curl, CURLOPT_POST, true);
            if (is_array($param)) {
                $param = http_build_query($param);
            }

            curl_setopt($curl, CURLOPT_POSTFIELDS, $param);
        } else {
            curl_setopt($curl, CURLOPT_POST, false);
        }

        // 执行输出
        $info = curl_exec($curl);

//        log
        $_errno = curl_errno($curl);
        $_error = '';
        if ($_errno) {
            $_error = curl_error($curl);
        }
        curl_close($curl);

        if ($_error) {
            return "curl出错，错误码：$_error";
        }

        return $info;
    }


    /*xml转JSON*/
    private static function FromXml($xml)
    {
        if (!$xml) {
            return false;
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);

        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }


    public static function Setpackage($res)
    {

        $package = self::FromXml($res);
        if ($package['return_code'] == 'SUCCESS' and $package['result_code'] == "SUCCESS"){
            return ['code' => 0,'msg' => $package];
        }elseif($package['return_code'] == 'SUCCESS' and $package['result_code'] != "SUCCESS"){
            return ['code' => 99, 'msg' => $package['err_code_des']];
        }else{
            return ['code' => 99, 'msg' => $package['return_msg']];
        }
    }


    public static function SetPayPackage($package)
    {
        self::$value = [
            'appId' => self::$appid,
            'timeStamp' => time(),
            'nonceStr' => self::createSn(),
            'package' => 'prepay_id='.$package['prepay_id'],
            'signType' => 'MD5',
        ];
        self::$value['paySign'] = self::ToUrlParams();
        return self::$value;
    }




    public static function recordLogin($code,$msg,$data)
    {
        $log = new FileTarget();
        $log->logFile = Yii::$app->getRuntimePath() . '/wxPay/api_' . date('Ymd') . '.log';
        $log->messages[] = [
            "[url:{$_SERVER['REQUEST_URI']}],[data:{$code},{$msg},{$data}]",
            1,
            'application',
            time(),
        ];
        $log->export();
    }

}